<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue 批量事件绑定实现</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      font-family: 'Segoe UI', system-ui, sans-serif;
    }
    
    body {
      background: linear-gradient(135deg, #f5f7fa, #e4edf9);
      color: #2c3e50;
      min-height: 100vh;
      padding: 30px;
      line-height: 1.6;
    }
    
    .container {
      max-width: 1000px;
      margin: 0 auto;
    }
    
    header {
      text-align: center;
      margin-bottom: 40px;
    }
    
    h1 {
      font-size: 2.5rem;
      background: linear-gradient(45deg, #4361ee, #3a0ca3);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      margin-bottom: 15px;
      font-weight: 700;
    }
    
    .subtitle {
      color: #4a5568;
      font-size: 1.1rem;
      max-width: 700px;
      margin: 0 auto;
    }
    
    .panel {
      background: white;
      border-radius: 12px;
      box-shadow: 0 10px 35px rgba(0, 0, 0, 0.1);
      padding: 30px;
      margin-bottom: 30px;
    }
    
    .panel-header {
      display: flex;
      align-items: center;
      margin-bottom: 25px;
      padding-bottom: 15px;
      border-bottom: 2px solid #4361ee;
    }
    
    .panel-header h2 {
      color: #3a0ca3;
      font-size: 1.8rem;
    }
    
    .badge {
      background: #4361ee;
      color: white;
      padding: 3px 12px;
      border-radius: 20px;
      font-size: 0.9rem;
      margin-left: 15px;
      font-weight: 600;
    }
    
    .interactive-area {
      min-height: 300px;
      padding: 25px;
      border: 2px dashed #4361ee66;
      border-radius: 10px;
      background: #f9fafe;
      position: relative;
    }
    
    .interactive-area::before {
      content: "交互区域";
      position: absolute;
      top: -12px;
      left: 15px;
      background: #4361ee;
      color: white;
      padding: 0 10px;
      font-size: 14px;
      border-radius: 4px;
    }
    
    .event-target {
      width: 100%;
      height: 200px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #f0f4ff;
      border-radius: 8px;
      margin: 20px 0;
      font-size: 1.2rem;
      font-weight: 600;
      color: #3a0ca3;
      text-align: center;
      cursor: pointer;
      transition: all 0.3s ease;
      position: relative;
      overflow: hidden;
    }
    
    .event-target:hover {
      transform: scale(1.02);
      box-shadow: 0 8px 25px rgba(67, 97, 238, 0.25);
    }
    
    .event-target::after {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: linear-gradient(135deg, rgba(67, 97, 238, 0.1), rgba(58, 12, 163, 0.1));
      z-index: 1;
    }
    
    .event-target-content {
      position: relative;
      z-index: 2;
      padding: 20px;
    }
    
    .events-panel {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      gap: 15px;
      margin-top: 20px;
    }
    
    .event-item {
      background: white;
      border: 2px solid #4361ee66;
      border-radius: 8px;
      padding: 15px;
      text-align: center;
      cursor: pointer;
      transition: all 0.2s ease;
    }
    
    .event-item:hover {
      transform: translateY(-3px);
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
    }
    
    .event-item.active {
      background: #4361ee;
      color: white;
      border-color: #4361ee;
    }
    
    .event-item h3 {
      font-size: 1.1rem;
      margin-bottom: 8px;
    }
    
    .event-item p {
      font-size: 0.9rem;
      color: #718096;
    }
    
    .event-item.active p {
      color: rgba(255, 255, 255, 0.8);
    }
    
    .controls {
      display: flex;
      gap: 15px;
      margin-top: 25px;
      flex-wrap: wrap;
    }
    
    .btn {
      padding: 12px 25px;
      border: none;
      border-radius: 8px;
      background: #4361ee;
      color: white;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s ease;
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      box-shadow: 0 4px 6px rgba(67, 97, 238, 0.2);
    }
    
    .btn:hover {
      background: #3a0ca3;
      transform: translateY(-3px);
      box-shadow: 0 7px 14px rgba(58, 12, 163, 0.25);
    }
    
    .btn i {
      margin-right: 8px;
    }
    
    .btn-outline {
      background: transparent;
      border: 2px solid #4361ee;
      color: #4361ee;
    }
    
    .btn-outline:hover {
      background: #4361ee;
      color: white;
    }
    
    .event-log {
      background: #011627;
      color: #d6deeb;
      border-radius: 8px;
      padding: 20px;
      font-family: 'Fira Code', Consolas, monospace;
      overflow-y: auto;
      max-height: 300px;
      margin-top: 25px;
      position: relative;
    }
    
    .event-log::before {
      content: "事件日志";
      position: absolute;
      top: -12px;
      left: 15px;
      background: #011627;
      color: #82aaff;
      padding: 0 10px;
      font-size: 14px;
      border-radius: 4px;
    }
    
    .log-entry {
      margin-bottom: 10px;
      padding-bottom: 10px;
      border-bottom: 1px solid #1e2d3d;
    }
    
    .log-entry:last-child {
      border-bottom: none;
      margin-bottom: 0;
      padding-bottom: 0;
    }
    
    .log-time {
      color: #637777;
      font-size: 0.85rem;
      margin-right: 10px;
    }
    
    .log-type {
      color: #82aaff;
      font-weight: 600;
    }
    
    .log-details {
      color: #d6deeb;
      margin-left: 10px;
    }
    
    .stats {
      display: flex;
      justify-content: space-between;
      margin-top: 15px;
      padding-top: 15px;
      border-top: 1px solid #e2e8f0;
    }
    
    .stat-item {
      text-align: center;
    }
    
    .stat-value {
      font-size: 1.8rem;
      font-weight: 700;
      color: #3a0ca3;
    }
    
    .stat-label {
      font-size: 0.9rem;
      color: #718096;
    }
    
    .footer {
      text-align: center;
      margin-top: 40px;
      color: #718096;
      padding: 20px;
    }
    
    .explanation {
      background: #f0f8ff;
      border-radius: 8px;
      padding: 20px;
      margin-top: 25px;
    }
    
    .explanation h3 {
      color: #4361ee;
      margin-bottom: 15px;
    }
    
    .explanation ul {
      padding-left: 25px;
    }
    
    .explanation li {
      margin-bottom: 8px;
      line-height: 1.5;
    }
    
    .highlight {
      background: rgba(67, 97, 238, 0.1);
      padding: 2px 6px;
      border-radius: 4px;
      color: #3a0ca3;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <header>
        <h1>Vue 批量事件绑定实现</h1>
        <p class="subtitle">动态管理多个事件监听器，实现高效的事件处理</p>
      </header>
      
      <div class="panel">
        <div class="panel-header">
          <h2>事件绑定控制面板</h2>
          <span class="badge">当前绑定事件: {{ activeEvents.length }}</span>
        </div>
        
        <div class="interactive-area">
          <div class="event-target" ref="eventTarget">
            <div class="event-target-content">
              <p>事件目标区域</p>
              <p>当前绑定事件: {{ activeEvents.length }}</p>
              <p>事件触发总数: {{ eventCount }}</p>
            </div>
          </div>
          
          <div class="events-panel">
            <div 
              v-for="event in availableEvents" 
              :key="event.type"
              class="event-item"
              :class="{ active: isEventActive(event.type) }"
              @click="toggleEvent(event.type)"
            >
              <h3>{{ event.name }}</h3>
              <p>{{ event.description }}</p>
            </div>
          </div>
          
          <div class="controls">
            <button class="btn" @click="bindAllEvents">
              <i>🔗</i> 绑定所有事件
            </button>
            <button class="btn btn-outline" @click="unbindAllEvents">
              <i>🚫</i> 移除所有事件
            </button>
            <button class="btn btn-outline" @click="clearEventLog">
              <i>🧹</i> 清除日志
            </button>
          </div>
        </div>
      </div>
      
      <div class="panel">
        <div class="panel-header">
          <h2>事件日志</h2>
          <span class="badge">总数: {{ eventLog.length }}</span>
        </div>
        
        <div class="event-log">
          <div v-for="(log, index) in eventLog" :key="index" class="log-entry">
            <span class="log-time">{{ log.time }}</span>
            <span class="log-type">{{ log.type }}</span>
            <span class="log-details">{{ log.details }}</span>
          </div>
          
          <div v-if="eventLog.length === 0" style="color: #637777; text-align: center;">
            暂无事件记录，请触发事件...
          </div>
        </div>
        
        <div class="stats">
          <div class="stat-item">
            <div class="stat-value">{{ eventCount }}</div>
            <div class="stat-label">事件总数</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ activeEvents.length }}</div>
            <div class="stat-label">当前绑定</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ uniqueEvents.size }}</div>
            <div class="stat-label">事件类型</div>
          </div>
        </div>
      </div>
      
      <div class="panel">
        <div class="panel-header">
          <h2>实现说明</h2>
        </div>
        
        <div class="explanation">
          <h3>Vue 批量事件绑定技术要点</h3>
          <ul>
            <li><span class="highlight">动态事件绑定</span>：使用 Vue 的 <code>v-on</code> 指令或动态事件对象实现批量绑定</li>
            <li><span class="highlight">事件管理</span>：维护事件状态数组，动态添加/移除事件监听器</li>
            <li><span class="highlight">自动清理</span>：在组件卸载时自动移除所有事件监听器</li>
            <li><span class="highlight">事件委托</span>：对于相似事件，可使用事件委托提高性能</li>
            <li><span class="highlight">响应式日志</span>：实时记录和显示事件触发信息</li>
          </ul>
          
          <h3>核心代码实现</h3>
          <pre style="background: #f5f7fa; padding: 15px; border-radius: 6px; overflow-x: auto;">
// 批量绑定事件
bindEvents() {
  this.activeEvents.forEach(eventType => {
    this.$refs.eventTarget.addEventListener(eventType, this.eventHandler);
  });
},

// 事件处理函数
eventHandler(event) {
  const eventType = event.type;
  const details = this.getEventDetails(event);
  
  this.eventLog.unshift({
    type: eventType,
    time: new Date().toLocaleTimeString(),
    details: details
  });
  
  this.eventCount++;
  this.uniqueEvents.add(eventType);
},

// 动态添加事件
addEvent(eventType) {
  if (!this.activeEvents.includes(eventType)) {
    this.activeEvents.push(eventType);
    this.$refs.eventTarget.addEventListener(eventType, this.eventHandler);
  }
},

// 动态移除事件
removeEvent(eventType) {
  const index = this.activeEvents.indexOf(eventType);
  if (index > -1) {
    this.activeEvents.splice(index, 1);
    this.$refs.eventTarget.removeEventListener(eventType, this.eventHandler);
  }
}</pre>
        </div>
      </div>
      
      <div class="footer">
        <p>Vue 批量事件绑定实现 | 高效管理多个事件监听器</p>
      </div>
    </div>
  </div>

  <script>
    const { createApp, ref, reactive, onMounted, onBeforeUnmount } = Vue;
    
    const app = createApp({
      setup() {
        // 可用事件类型
        const availableEvents = reactive([
          { type: 'click', name: '点击事件', description: '鼠标点击时触发' },
          { type: 'dblclick', name: '双击事件', description: '鼠标双击时触发' },
          { type: 'mouseover', name: '鼠标悬停', description: '鼠标移入元素时触发' },
          { type: 'mouseout', name: '鼠标移出', description: '鼠标移出元素时触发' },
          { type: 'mousemove', name: '鼠标移动', description: '鼠标在元素上移动时触发' },
          { type: 'keydown', name: '按键按下', description: '键盘按键按下时触发' },
          { type: 'keyup', name: '按键释放', description: '键盘按键释放时触发' },
          { type: 'focus', name: '获得焦点', description: '元素获得焦点时触发' },
          { type: 'blur', name: '失去焦点', description: '元素失去焦点时触发' },
          { type: 'scroll', name: '滚动事件', description: '元素滚动时触发' }
        ]);
        
        // 当前激活的事件类型
        const activeEvents = ref([]);
        
        // 事件日志
        const eventLog = ref([]);
        
        // 事件计数器
        const eventCount = ref(0);
        
        // 唯一事件类型集合
        const uniqueEvents = ref(new Set());
        
        // 检查事件是否已激活
        const isEventActive = (eventType) => {
          return activeEvents.value.includes(eventType);
        };
        
        // 切换事件绑定状态
        const toggleEvent = (eventType) => {
          if (isEventActive(eventType)) {
            removeEvent(eventType);
          } else {
            addEvent(eventType);
          }
        };
        
        // 添加单个事件
        const addEvent = (eventType) => {
          if (!activeEvents.value.includes(eventType)) {
            activeEvents.value.push(eventType);
            eventTarget.value.addEventListener(eventType, eventHandler);
          }
        };
        
        // 移除单个事件
        const removeEvent = (eventType) => {
          const index = activeEvents.value.indexOf(eventType);
          if (index > -1) {
            activeEvents.value.splice(index, 1);
            eventTarget.value.removeEventListener(eventType, eventHandler);
          }
        };
        
        // 绑定所有事件
        const bindAllEvents = () => {
          availableEvents.forEach(event => {
            if (!isEventActive(event.type)) {
              addEvent(event.type);
            }
          });
        };
        
        // 移除所有事件
        const unbindAllEvents = () => {
          // 创建副本以避免修改正在迭代的数组
          const eventsToRemove = [...activeEvents.value];
          eventsToRemove.forEach(eventType => {
            removeEvent(eventType);
          });
        };
        
        // 清除事件日志
        const clearEventLog = () => {
          eventLog.value = [];
          eventCount.value = 0;
          uniqueEvents.value.clear();
        };
        
        // 事件处理函数
        const eventHandler = (event) => {
          const eventType = event.type;
          const details = getEventDetails(event);
          
          // 添加到日志
          eventLog.value.unshift({
            type: eventType,
            time: new Date().toLocaleTimeString(),
            details: details
          });
          
          // 更新统计
          eventCount.value++;
          uniqueEvents.value.add(eventType);
          
          // 限制日志长度
          if (eventLog.value.length > 100) {
            eventLog.value.pop();
          }
        };
        
        // 获取事件详细信息
        const getEventDetails = (event) => {
          switch (event.type) {
            case 'click':
            case 'dblclick':
              return `坐标: (${event.clientX}, ${event.clientY})`;
            case 'mouseover':
            case 'mouseout':
            case 'mousemove':
              return `位置: (${event.clientX}, ${event.clientY})`;
            case 'keydown':
            case 'keyup':
              return `按键: ${event.key} (代码: ${event.code})`;
            case 'focus':
            case 'blur':
              return `元素: ${event.target.tagName}`;
            case 'scroll':
              return `滚动位置: ${event.target.scrollTop}px`;
            default:
              return event.type;
          }
        };
        
        // 事件目标元素
        const eventTarget = ref(null);
        
        // 组件挂载时绑定初始事件
        onMounted(() => {
          // 初始绑定点击和鼠标悬停事件
          addEvent('click');
          addEvent('mouseover');
        });
        
        // 组件卸载前移除所有事件监听器
        onBeforeUnmount(() => {
          unbindAllEvents();
        });
        
        return {
          availableEvents,
          activeEvents,
          eventLog,
          eventCount,
          uniqueEvents,
          eventTarget,
          isEventActive,
          toggleEvent,
          bindAllEvents,
          unbindAllEvents,
          clearEventLog
        };
      }
    });
    
    app.mount('#app');
  </script>
</body>
</html>